/*
  HardwareSerial.cpp - Hardware serial library for Wiring
  Copyright (c) 2006 Nicholas Zambetti.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  
  Modified 23 November 2006 by David A. Mellis
  Modified 28 September 2010 by Mark Sproul
  Modified 14 August 2012 by Alarus
  Modified 3 December 2013 by Matthijs Kooijman
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>

#include "HardwareSerial.h"

/**
 * @brief Serial object construction.
 * 
 */
HardwareSerial::HardwareSerial(void *uart, size_t bufferSize, bool use_dma) :
	_uart(uart),
	_isDMA(use_dma),
	txBuffer(bufferSize),
	rxBuffer(bufferSize) {
}

HardwareSerial::~HardwareSerial() {
}

// Public Methods //////////////////////////////////////////////////////////////

void HardwareSerial::begin(uint32_t baud, Callback_t call, bool use_dma) {
	_isDMA = use_dma;

	flush();
	flushForWrite();
	callback = call;

	/* uart init */
	SERIAL_BEGIN(_uart, baud);

	/* start rx dma */
	rxBuffer.pushStart();
	SERIAL_READ(_uart, rxBuffer.head(), rxBuffer.getPushLineSize());
	rxDma.buffer_size = rxBuffer.getPushLineSize();
}

void HardwareSerial::end() {
	// wait for transmission of outgoing data
	flush();
	flushForWrite();
	// clear any received data
}

int HardwareSerial::peek() {
	return rxBuffer.peek();
}

int HardwareSerial::read() {
	// if the head isn't ahead of the tail, we don't have any characters
	if (available() == 0) {
		return -1;
	}

	uint8_t c;
	size_t rd_size = read(&c, 1);

	return rd_size > 0 ? c : -1;
}

size_t HardwareSerial::read(uint8_t *buffer, size_t length) {
	return rxBuffer.pull(buffer, length);
}

void HardwareSerial::flush() {
	rxBuffer.flush();
}

void HardwareSerial::flushForWrite() {
	txBuffer.flush();
}

size_t HardwareSerial::write(uint8_t c) {
	write(&c, 1);
	return 1;
}

size_t HardwareSerial::write(const uint8_t *buffer, size_t size) {
	size_t len = txBuffer.push((uint8_t *) buffer, size);

	writeToUart(0);
	return len;
}

bool HardwareSerial::readFormUart(uint32_t has_read) {
	rxBuffer.pushEnd(has_read);
	rxBuffer.pushStart();
	return false;
}

bool HardwareSerial::writeToUart(uint32_t written_len) {
	txBuffer.pullEnd(written_len);

	if (txDma.buys) {
		return false;
	}

	if (availableForWrite() == 0) {
		return false;
	}

	txBuffer.pullStart();
	uint32_t rxSize   = txBuffer.getPullLineSize();
	txDma.buffer_size = rxSize;
	SERIAL_WRITE(_uart, txBuffer.tail(), rxSize);
	txDma.buys = true;

	return true;
}

void HardwareSerial::irqHandler(void *uart, int event, uint32_t idleRxCount) {
	if (uart != _uart) {
		return;
	}

	if (callback) {
		callback(this, (SerialEvent_t) event);
	}

	uint32_t transfer_size = 0;

	/* UART Idle interrupt handler */
	if (event == SERIAL_EVENT_IDLE) {
		//        txDma.buys = false;
		rxDma.buys = false;

		/* DMA Tx interrupt idle handler */
		txDma.event = SERIAL_EVENT_IDLE;

		/* DMA Rx interrupt idle handler */
		if (rxDma.event & SERIAL_EVENT_RX_HALF) {
			transfer_size = idleRxCount - (rxDma.buffer_size >> 1) - rxDma.last_transfer_cnt;
		} else if (rxDma.event & SERIAL_EVENT_RX_TC) {
			rxDma.event   = SERIAL_EVENT_IDLE;
			transfer_size = idleRxCount;
		} else {
			/* Not happen DMA interrupt */
			transfer_size = idleRxCount - rxDma.last_transfer_cnt;
		}

		rxDma.last_transfer_cnt += transfer_size;
		readFormUart(transfer_size);
		return;
	}

	/* DMA Tx interrupt handler */
	if (event & (SERIAL_EVENT_TX_HALF | SERIAL_EVENT_TX_TC)) {
		uint32_t tx_size = (event == SERIAL_EVENT_TX_HALF) ? txDma.buffer_size >> 1 : (txDma.buffer_size + 1) >> 1;

		txDma.event = (SerialEvent_t) event;
		if (event == SERIAL_EVENT_TX_TC) {
			txDma.buys = false;
		}

		writeToUart(tx_size);
		return;
	}

	/* DMA Rx interrupt handler */
	if (event & (SERIAL_EVENT_RX_HALF | SERIAL_EVENT_RX_TC)) {
		uint16_t rx_size = (rxDma.buffer_size >> 1) - rxDma.last_transfer_cnt;

		rxDma.event             = (SerialEvent_t) event;
		rxDma.last_transfer_cnt = 0;

		readFormUart(rx_size);
		return;
	}
}

#if SERIAL_1_ENABLE
HardwareSerial Serial(SERIAL_1_OBJ);
#endif

#if SERIAL_2_ENABLE
HardwareSerial Serial2(SERIAL_2_OBJ);
#endif

#if SERIAL_3_ENABLE
HardwareSerial Serial3(SERIAL_3_OBJ);
#endif

extern "C" void SERIAL_EVENT_HANDLER(void *uart, int event, uint32_t rxCount) {
#if SERIAL_1_ENABLE
	Serial.irqHandler(uart, event, rxCount);
#endif

#if SERIAL_2_ENABLE
	Serial2.irqHandler(uart, event, rxCount);
#endif

#if SERIAL_3_ENABLE
	Serial3.irqHandler(uart, event, rxCount);
#endif
}
